package Network;

import Graph.ForceGraphDrawer;
import Graph.Graph;
import Graph.GraphData;
import Graph.GraphDrawer;
import data.Table;
import data.TableData;
import java.awt.Color;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import perspectives.Property;
import perspectives.PropertyManager;
import util.NodeLinkViewer;
import util.Points2DViewer;
import edu.fiu.cs.alg.clustering.*;
import edu.fiu.cs.core.Cluster;
import edu.fiu.cs.core.DoubleArray;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.Random;
import Graph.*;
import edu.fiu.cs.util.NodeSearcher;
import java.awt.BasicStroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.io.PrintWriter;

/**
 *
 * @author mokoe001
 */
public class NetworkDrawer extends NodeLinkViewer {

    Graph graph;
    GraphDrawer drawer;
    int[] edgeSources;
    int[] edgeTargets;
    private Table t;
    HashMap nodes; // to hold the nodes
    HashMap edges; // to hold the edges
    double clustWeightForInflows = 1;
    double clustWeightForOutflows = 1;
    double clustWeightForBytesRecvd = 1;
    double clustWeightForBytesSent = 1;
    int clusterSize;
    final Color[] clusterColors = {Color.blue, Color.RED, Color.GREEN,
        Color.YELLOW, Color.MAGENTA, Color.ORANGE,
        Color.PINK, Color.darkGray, Color.CYAN,
        Color.LIGHT_GRAY
    };
    java.util.List<Cluster> clusters;
    public boolean newNodeSelected = false;
    String selectedNodeInformation = "";
    //variables for searching
    boolean srchIpAddress;
    String srchIpAddressValue;
    String srchIPAddressCriteria = "=";
    boolean srchInflows;
    String srchInflowsValue;
    String srchInflowsCriteria = "=";
    boolean srchOutflows;
    String srchOutflowsValue;
    String srchOutflowsCriteria = "=";
    boolean srchBytesSent;
    String srchBytesSentValue;
    String srchBytesSentCriteria = "=";
    boolean srchBytesRecvd;
    String srchBytesRecvdValue;
    String srchBytesRecvdCriteria = "=";
    ArrayList<String> srchAttributeName;
    ArrayList<String> srchAttributeValue;
    ArrayList<String> srchAttribuuteCriteria;

    public NetworkDrawer(String name, TableData tab) {
        super(name);
        // clustWeightForInflows = 1.0; //default value
        clusterSize = 4; // default value for clusterSize.
        graph = new Graph(false);
        nodes = new HashMap();
        edges = new HashMap();
        t = tab.getTable();

        String srcIP, destIP;

        for (int i = 0; i < t.getRowCount(); i++) {

            srcIP = t.getValueAt(i, 0).toString();

            destIP = t.getValueAt(i, 3).toString();

            // add the nodes to the graph
            graph.addEdge(srcIP, destIP);

        }

        ArrayList<Integer> e1 = new ArrayList<Integer>();
        ArrayList<Integer> e2 = new ArrayList<Integer>();

        graph.getEdgesAsIndeces(e1, e2);

        edgeSources = new int[e1.size()];
        edgeTargets = new int[e2.size()];
        for (int i = 0; i < e1.size(); i++) {
            edgeSources[i] = e1.get(i);
            edgeTargets[i] = e2.get(i);
        }

        drawer = new BarnesHutGraphDrawer(graph);

        populateNodeAndEdges(t); // populating the nodes and edges hash maps
        // with the data

        try {

            Property<Boolean> p2 = new Property<Boolean>("Simulation.Simulate");
            p2.setValue(new Boolean(false));
            this.addProperty(p2);

        } catch (Exception e) {
            e.printStackTrace();
        }

        // testing clustering algorithm
        computeNodeClusters();



    }

    public double getClustWeightForInflows() {
        return clustWeightForInflows;
    }

    public void setClustWeightForInflows(double clustWeightForInflows) {
        this.clustWeightForInflows = clustWeightForInflows;
    }

    public double getClustWeightForOutflows() {
        return clustWeightForOutflows;
    }

    public void setClustWeightForOutflows(double clustWeightForOutflows) {
        this.clustWeightForOutflows = clustWeightForOutflows;
    }

    public double getClustWeightForBytesRecvd() {
        return clustWeightForBytesRecvd;
    }

    public void setClustWeightForBytesRecvd(double clustWeightForBytesRecvd) {
        this.clustWeightForBytesRecvd = clustWeightForBytesRecvd;

        // computeNodeClusters();
    }
    //set the colors
//        this.setColor(Color.red);

    public double getClustWeightForBytesSent() {
        return clustWeightForBytesSent;
    }

    public void setClustWeightForBytesSent(double clustWeightForBytesSent) {
        this.clustWeightForBytesSent = clustWeightForBytesSent;
    }

    public int getClusterSize() {
        return clusterSize;
    }

    public void setClusterSize(int clusterSize) {
        this.clusterSize = clusterSize;
    }

    public void simulate() {

        boolean b = (Boolean) this.getProperty("Simulation.Simulate")
                .getValue();
        if (b) {
            long t = new Date().getTime();
            drawer.iteration();
        }
    }

    /**
     * Populating the hashmaps of nodes and links with the data
     *
     * @param t
     */
    public void populateNodeAndEdges(Table t) {
        // HashMap iPs = new HashMap();
        String srcIP, destIP;
        // get the source and destination addresses and add them to the map
        int cnt = 0;
        int duplicate = 0;
        int total = 0;
        for (int i = 0; i < t.getRowCount(); i++) {
            total++;
            srcIP = t.getValueAt(i, 0).toString();
            if (nodes.containsKey(srcIP)) {
                duplicate++;

                Node node = (Node) nodes.get(srcIP);

                node.incrementNumOfOutFlow();

                int bytesSent = Integer.parseInt(t.getValueAt(i, 5).toString());// the
                // length
                // can
                // be
                // found
                // in
                // the
                // 5th
                // column
                node.updateBytesSent(bytesSent);

                nodes.put(srcIP, node);

                // System.out.println("Source Duplicate on "+i);
            } else {
                int bytesSent = Integer.parseInt(t.getValueAt(i, 5).toString());// the
                // length
                // can
                // be
                // found
                // in
                // the
                // 5th
                // column

                Node node = new Node(cnt, srcIP, 0, 1, bytesSent, 0);

                nodes.put(srcIP, node);

                cnt++;
            }

            destIP = t.getValueAt(i, 3).toString();

            if (nodes.containsKey(destIP)) {
                duplicate++;
                Node node = (Node) nodes.get(destIP);

                node.incrementNumOfOutFlow();

                int bytesRecv = Integer.parseInt(t.getValueAt(i, 5).toString());// the
                // length
                // can
                // be
                // found
                // in
                // the
                // 5th
                // column
                node.updateBytesReceived(bytesRecv);

                nodes.put(destIP, node);

                // System.out.println("Destination duplicate on "+i);

            } else {

                int bytesRecv = Integer.parseInt(t.getValueAt(i, 5).toString());// the
                // length
                // can
                // be
                // found
                // in
                // the
                // 5th
                // column

                Node node = new Node(cnt, destIP, 1, 0, bytesRecv, 0);

                nodes.put(destIP, node);

                cnt++;

            }

            // add the nodes to the graph
            graph.addEdge(srcIP, destIP);
            int number = Integer.parseInt(t.getValueAt(i, 1).toString());
            String time = t.getValueAt(i, 2).toString();
            String protocol = t.getValueAt(i, 4).toString();
            int length = Integer.parseInt(t.getValueAt(i, 5).toString());
            String info = t.getValueAt(i, 6).toString();
            String id = srcIP + "-" + destIP;

            if (!edges.containsKey(id)) {
                Edge edge = new Edge(srcIP, number, destIP, time, protocol, length, info);
                edges.put(id, edge);
            }


        }

        System.out.println("Duplicates; " + duplicate + ",  Unique: " + cnt
                + " Total: " + total);


    }

    public void computeNodeClusters() {
        // convert the hashmap of nodes to a list
        ArrayList<Node> nodesList = new ArrayList<Node>(nodes.values());

        // set the cluster weights of #inflows, #outflows, #bytesSent,
        // #bytesReceived
        // double weights[] = {clustWeightForInflows, clustWeightForOutflows,
        // clustWeightForBytesSent, clustWeightForBytesRecvd};


        this.recomputeClusterWeights();

        double weights[] = {clustWeightForInflows, clustWeightForOutflows,
            clustWeightForBytesRecvd, clustWeightForBytesSent};

        System.out.println("The weights are:  ");
        for (int i = 0; i < weights.length; i++) {
            System.out.print(weights[i] + "  ");
        }
        System.out.println();



        // a double array for the weights
        DoubleArray doubleArray = new DoubleArray(weights);
        int k = clusterSize;
        // call the clustering algorithms
        AlgoKMeans kmeans = new AlgoKMeans();
        // do clustering, and then return a list of clusters
        clusters = kmeans.runAlgorithm(nodesList,
                doubleArray, k);

        /*    Cluster[] clstr = new Cluster[2];
         System.out.println("************************************************************");
         System.out.println("************************************************************");
         System.out.println("Print Cluster:");
         for (int i = 0; i < clusters.size(); i++) {

         System.out
         .println("************************************************************");
         System.out.println(clusters.get(i));
         System.out
         .println("-------------------------------------------------------------");

         }*/

        setNodeClusterColors();
        
        writeClusterToFile();

    }

    public void printTableData(Table t) {
        System.out.println("Printing the data in NETWORK DRAWER");
        for (int i = 0; i < t.getRowCount(); i++) {

            System.out.print(i + ":\t");
            for (int j = 0; j < t.getColumnCount(); j++) {
                System.out.print(" " + t.getValueAt(i, j).toString());
            }
            System.out.println();

        }
    }

    @Override
    protected int getNumberOfNodes() {
        return graph.numberOfNodes();
    }

    @Override
    protected int getNodeX(int p) {
        return (int) drawer.getX(p);
    }

    @Override
    protected int getNodeY(int p) {
        return (int) drawer.getY(p);
    }

    @Override
    protected void setNodeX(int p, int x) {
        drawer.setX(p, x);
    }

    @Override
    protected void setNodeY(int p, int y) {
        drawer.setY(p, y);
    }

    @Override
    protected int[] getEdgeSources() {
        return edgeSources;
    }

    @Override
    protected int[] getEdgeTargets() {
        return edgeTargets;
    }

    /**
     * Set the color based on the node colors
     *
     * @param c Se
     */
    @Override
    public void setColor(Color c) {
        if (pointColor == null) {
            pointColor = new Color[getNumberOfPoints()];
        }
        int j = 0;

        for (int i = 0; i < pointColor.length; i++) {

            //if there is enough unique color, color with a unique color
            //otherwise color by a percentage of the previous color
            if (j == clusterColors.length) {
                j = 0;
            }
            pointColor[i] = ((Node) nodes.get(getPointLabel(i))).getNodeColor();

            //System.out.println(getPointLabel(i));

            j++;
        }

        updateBounds();

    }

    public void setNodeClusterColors() {

        //Color prevColor
        Color color = Color.gray;
        for (int i = 0; i < clusters.size(); i++) {
            java.util.List<Node> clusterX = clusters.get(i).getVectors();
            System.out.println("The size of cluster " + i + " is: " + clusterX.size());
            for (int j = 0; j < clusterX.size(); j++) {
                //get the node from the hashmap and set the color.

                Node node = clusterX.get(j);


                if (i < clusterColors.length) {
                    color = clusterColors[i];
                    // prevColor = clusterColors[i];
                } else {//set the color to be 80% of the previous RGB values.
                    color = new Color((int) (color.getRed() / 255 * 0.8), (int) (color.getGreen() / 255 * 0.8), (int) (color.getBlue() / 255 * 0.8));

                }
                //set the node color
                node.setNodeColor(clusterColors[i]);
                //put the node back into the nodes hashmap
                nodes.put(node.getiPAddress(), node);

            }
        }

        setColor(Color.red); //this will call the local color coding of the clusters.
    }

    @Override
    public boolean mousepressed(int x, int y, int button) {
        newNodeSelected = false;
        boolean cond;
        cond = super.mousepressed(x, y, button);
        if (cond) {
            int selectedNode = getPickedNode();
            //call the display node function
            displayNodeInformation(selectedNode);
            return true;
        } else {
            return false;
        }

    }

    /**
     * Display the information of a selected node
     *
     * @param selectedNode the integer Id of the selected node
     */
    public void displayNodeInformation(int selectedNode) {
        String nodeLabel = getNodeLabel(selectedNode);
        //get the node from the hashMap
        Node node = (Node) nodes.get(nodeLabel);
        selectedNodeInformation = "";
        if (node != null) { //just a check

            //set up a string of the node information 

            selectedNodeInformation = "IP address: " + node.getiPAddress() + " \n"
                    + "#Inflows: " + node.getNumOfInflow() + " \n"
                    + "#Outflows: " + node.getNumOfOutflow() + " \n"
                    + "Bytes Sent: " + node.getBytesSent() + "  \n"
                    + "Bytes Received: " + node.getBytesReceived();
        }
        //set the tooltip
        newNodeSelected = true;
        // this.setToolTipText(nodeInformation);
    }

    @Override
    public void renderTooltip(Graphics2D g) {

        if (!newNodeSelected) {
            super.renderTooltip(g);

        } else {

            g.setFont(new Font("Sans-serif", Font.PLAIN, 11));

            String[] labelText = selectedNodeInformation.split("\n"); //split it by the nextline character

            //the line with the IP address will be longer, so we will  use that as the sizeX
            int sizeX = g.getFontMetrics().stringWidth(labelText[0]) + 15;
            int sizeY = g.getFontMetrics().getHeight();

            int numberOfLines = 0;

            int toolTipX = getTooltipX() + 15; //+15 because we dont want the label to be covered by the mouse
            int toolTipY = getTooltipY();


            for (int i = 0; i < labelText.length; i++) {
                g.setColor(new Color(235, 235, 100));
                g.fillRect(toolTipX, (toolTipY + (numberOfLines * sizeY) + 2) - sizeY, sizeX, sizeY);
                g.setColor(Color.DARK_GRAY);
                g.drawString(labelText[i], toolTipX, toolTipY + (numberOfLines * sizeY) - 3);
                numberOfLines++;
            }
            //draw a rectangle or a bounding box around all the information.
            g.drawRect(toolTipX - 2, toolTipY - sizeY + 2, sizeX, sizeY * numberOfLines);

        }

    }

    public void searchForNodes() {
        //first recolor with the main cluster colors.
        setNodeClusterColors();


        //check and set the parameters for the set.
        //call a method that searches and returns a list of nodes that meet the criteria.

        srchAttributeName = new ArrayList<String>();
        srchAttributeValue = new ArrayList<String>();
        srchAttribuuteCriteria = new ArrayList<String>();
        //check for all the attributes
        if (isSrchIpAddress()) {
            srchAttributeName.add("ipAddress");
            srchAttributeValue.add(srchIpAddressValue);
            srchAttribuuteCriteria.add(srchIPAddressCriteria);
        }
        if (isSrchInflows()) {
            srchAttributeName.add("numOfInFlows");
            srchAttributeValue.add(srchInflowsValue);
            srchAttribuuteCriteria.add(srchInflowsCriteria);
        }
        if (isSrchOutflows()) {
            srchAttributeName.add("numOfOutFlows");
            srchAttributeValue.add(srchOutflowsValue);
            srchAttribuuteCriteria.add(srchOutflowsCriteria);
        }
        if (isSrchBytesRecvd()) {
            srchAttributeName.add("bytesReceived");
            srchAttributeValue.add(srchBytesRecvdValue);
            srchAttribuuteCriteria.add(srchBytesRecvdCriteria);
        }
        if (isSrchBytesSent()) {
            srchAttributeName.add("bytesSent");
            srchAttributeValue.add(srchBytesSentValue);
            srchAttribuuteCriteria.add(srchBytesSentCriteria);
        }

        //converts the arrayList to arrays.
        int size = srchAttributeName.size(); //all the arraylist will have the same sizes
        String attributeName[] = new String[size];
        String attributeValue[] = new String[size];
        String attributeCriteria[] = new String[size];

        attributeName = srchAttributeName.toArray(attributeName);
        attributeValue = srchAttributeValue.toArray(attributeValue);
        attributeCriteria = srchAttribuuteCriteria.toArray(attributeCriteria);


        /* for(int i=0; i<size; i++)      {
         System.out.println("Attrib-Name: "+attributeName[i]
         +"  attrib-value: "+ attributeValue[i]
         +"  attrib-criteria: "+attributeCriteria[i]);
         }*/




        // NodeSearcher nodeSearcher = new NodeSearcher();
        java.util.List<Node> resultNodes = NodeSearcher.searchNodes(nodes,
                attributeName, attributeCriteria, attributeValue);
        System.out.print("The size of the resulting nodes is : " + resultNodes.size());
        /* for(int i=0; i<resultNodes.size(); i++){
         System.out.println("--Node IP address: " + resultNodes.get(i).getiPAddress());
         }*/

        //set the color of all the resulting nodes to 
        for (int i = 0; i < resultNodes.size(); i++) {
            Node node = resultNodes.get(i);

            //set the node color to black
            node.setNodeColor(new Color(0, 0, 0));
            //put the node back into the nodes hashmap
            nodes.put(node.getiPAddress(), node);
        }

        setColor(Color.red); //this will call the local color coding of the clusters.


    }

    public void writeClusterToFile() {

        try {
            PrintWriter writer = new PrintWriter("ClusterFile.txt");


            for (int i = 0; i < clusters.size(); i++) {
                java.util.List<Node> clusterX = clusters.get(i).getVectors();
                System.out.println("The size of cluster " + i + " is: " + clusterX.size());
                for (int j = 0; j < clusterX.size(); j++) {
                    //get the node from the hashmap and set the color.

                    Node node = clusterX.get(j);


                    writer.println(node.getiPAddress() + ", " + i);
                }
            }
            
        } catch (Exception ex) {
            ex.printStackTrace();
        }


    }

    public boolean isSrchIpAddress() {
        return srchIpAddress;
    }

    public void setSrchIpAddress(boolean srchIpAddress) {
        this.srchIpAddress = srchIpAddress;
    }

    public String getSrchIpAddressValue() {
        return srchIpAddressValue;
    }

    public void setSrchIpAddressValue(String srchIpAddressValue) {
        this.srchIpAddressValue = srchIpAddressValue;
    }

    public String getSrchIPAddressCriteria() {
        return srchIPAddressCriteria;
    }

    public void setSrchIPAddressCriteria(String srchIPAddressCriteria) {
        this.srchIPAddressCriteria = srchIPAddressCriteria;
    }

    public boolean isSrchInflows() {
        return srchInflows;
    }

    public void setSrchInflows(boolean srchInflows) {
        this.srchInflows = srchInflows;
    }

    public String getSrchInflowsValue() {
        return srchInflowsValue;
    }

    public void setSrchInflowsValue(String srchInflowsValue) {
        this.srchInflowsValue = srchInflowsValue;
    }

    public String getSrchInflowsCriteria() {
        return srchInflowsCriteria;
    }

    public void setSrchInflowsCriteria(String srchInflowsCriteria) {
        this.srchInflowsCriteria = srchInflowsCriteria;
    }

    public boolean isSrchOutflows() {
        return srchOutflows;
    }

    public void setSrchOutflows(boolean srchOutflows) {
        this.srchOutflows = srchOutflows;
    }

    public String getSrchOutflowsValue() {
        return srchOutflowsValue;
    }

    public void setSrchOutflowsValue(String srchOutflowsValue) {
        this.srchOutflowsValue = srchOutflowsValue;
    }

    public String getSrchOutflowsCriteria() {
        return srchOutflowsCriteria;
    }

    public void setSrchOutflowsCriteria(String srchOutflowsCriteria) {
        this.srchOutflowsCriteria = srchOutflowsCriteria;
    }

    public boolean isSrchBytesSent() {
        return srchBytesSent;
    }

    public void setSrchBytesSent(boolean srchBytesSent) {
        this.srchBytesSent = srchBytesSent;
    }

    public String getSrchBytesSentValue() {
        return srchBytesSentValue;
    }

    public void setSrchBytesSentValue(String srchBytesSentValue) {
        this.srchBytesSentValue = srchBytesSentValue;
    }

    public String getSrchBytesSentCriteria() {
        return srchBytesSentCriteria;
    }

    public void setSrchBytesSentCriteria(String srchBytesSentCriteria) {
        this.srchBytesSentCriteria = srchBytesSentCriteria;
    }

    public boolean isSrchBytesRecvd() {
        return srchBytesRecvd;
    }

    public void setSrchBytesRecvd(boolean srchBytesRecvd) {
        this.srchBytesRecvd = srchBytesRecvd;
    }

    public String getSrchBytesRecvdValue() {
        return srchBytesRecvdValue;
    }

    public void setSrchBytesRecvdValue(String srchBytesRecvdValue) {
        this.srchBytesRecvdValue = srchBytesRecvdValue;
    }

    public String getSrchBytesRecvdCriteria() {
        return srchBytesRecvdCriteria;
    }

    public void setSrchBytesRecvdCriteria(String srchBytesRecvdCriteria) {
        this.srchBytesRecvdCriteria = srchBytesRecvdCriteria;
    }

    public void recomputeClusterWeights() {

        double totalWeights = clustWeightForInflows + clustWeightForOutflows
                + clustWeightForBytesRecvd + clustWeightForBytesSent;


        clustWeightForInflows = clustWeightForInflows / totalWeights;
        clustWeightForOutflows = clustWeightForOutflows / totalWeights;
        clustWeightForBytesRecvd = clustWeightForBytesRecvd / totalWeights;
        clustWeightForBytesSent = clustWeightForBytesSent / totalWeights;
    }

    @Override
    public boolean mousemoved(int x, int y) {
        if (newNodeSelected) {
            return false;
        } else {
            return super.mousemoved(x, y);
        }

    }

    @Override
    protected String getNodeLabel(int p) {
        return graph.getNodes().get(p);
    }

    @Override
    public Color backgroundColor() {
        return new Color(230, 230, 230);
    }
}
